"""
面向对象:
程序         现实中
对象 ----> 具体的事物

现实中事物 ----> 转化成电脑程序
世间万物皆对象

多个对象 ---> 提取对象的共同的特征和动作 ---> 封装到一个类中

类别：手机类 学生类
特征：姓名 年龄 性别 ---> 属性
动作：敲代码 看书 ---> 方法

所有的类名要求首字母大小写，多个单词使用驼峰式命名

class 类名[(父名)]:
    属性：特征

    方法：动作
"""


# 定义类
class Phone:
    # 类属性
    brand = "huawei"


# 使用类创建对象
ys = Phone()
print(ys)
print(ys.brand)  # 访问并打印对象类属性

zyl = Phone()
zyl.brand = "oppo"  # 动态创建对象属性
print(zyl.brand)  # 先找对象属性，再找类属性

# 修改类属性
Phone.brand = "xiaomi"
yz = Phone()
print(yz.brand, "\n")  # 对运行结果进行换行

# 类里面的方法：动作
# 种类：普通方法 类方法 静态方法 魔术方法
"""
普通方法格式：
def 方法名(self[参数，参数...]):
    pass
"""


class Phone:
    brand = "huawei"
    price = 5800
    type = "mate 60"

    def call(self):  # 参数self代表创建的对象本身
        print("正在拨号中...")
        print("留言：", self.note)


phone_1 = Phone()
phone_1.note = "hello phone_1"
phone_1.call()  # call(phone_1)

phone_2 = Phone()
phone_2.note = "hello phone_2"
phone_2.call()  # call(phone_2)
print("\n")  # 对终端显示结果进行隔行操作


class Phone:  # self随着调用对象而发生变化
    # 魔术方法之一，称作魔术方法：__名字__()
    def __init__(self):  # 方法__init__()相当于给了每个对象统一的格式，起到了标准化的作用
        self.brand = "xiaomi"
        self.price = 4999

    def call(self):
        print("-----> call")
        print("价格：", self.price)  # 不能保证每个self中都存在price


p1 = Phone()
p1.price = 1000
p1.call()

p2 = Phone()
p2.call()
print("\n")


class Person:
    # 初始化魔术方法
    def __init__(self, name, age):  # 构造方法的参数，用于在创建对象时传入初始值
        self.name = name  # 将传入的参数赋值给实例属性
        self.age = age

    def eat(self, food):
        print(f"{self.name}正在吃{food}")
        self.run()  # 类中方法的调用,需要通过self.方法名()

    def run(self):
        print(f"{self.name},今年{self.age},正在跑步")


p3 = Person("张三", 17)
p3.eat("小炒肉")
p3.run()

p4 = Person("李四", 18)
p4.eat("大锅饭")
p4.run()
print("\n")

"""
静态方法：很类似类方法
1.需要装饰器@staticmethod
2.静态方法无需传递参数（cls或self）
3.只能访问类的属性和方法，对象的无法访问
4.加载时机同类方法

总结：
类方法    静态方法
不同：
    1.装饰器不同
    2.类方法有参数，静态方法没有参数

相同：
    1.只能访问类的属性和方法，对象的无法访问
    2.都可以通过类名调用访问
    3.都可以在创建对象之前使用，因为是不依赖于对象的

普通方法与两者的区别：
    1.没有装饰器
    2.普通方法永远是要依赖对象的，因为每个普通方法都有一个self
    3.只有创建了对象才可以调用普通方法，否则无法调用
"""

"""
类方法
特点：
    1.定义需要依赖装饰器@classmethod
    2.类方法中参数不是一个对象，而是类
    3.类方法中只可以使用类属性
    4.类方法中不能使用普通方法
作用：
可以在对象创建之前完成一些动作（功能）
"""


class Person:
    __age = 18  # 私有类属性，只能在类内部访问

    def show(self):
        print("------>", Person.age)

    # 类方法通过@classmethod定义，参数cls表示类本身，用于访问类属性
    @classmethod
    def update_age(cls):
        cls.__age = 20  # 在类里面进行修改

    @classmethod
    def show_age(cls):
        print("修改后的年龄是：", cls.__age)

    @staticmethod
    def test():  # 静态方法不需要传参
        print("-------> 静态方法")


Person.update_age()  # 调用类方法更新年龄
Person.show_age()  # 调用类方法显示年龄
# Person.show()       这是一个实例方法，需要创建对象才能调用
print("\n")

"""
魔术方法
__init__:初始化魔术方法（重点，构造方法，创建完空间之后调用的第一个方法）
触发时机：初始化对象时触发（不是实例化触发，但是和实例化在一个操作中）

__new__:实例化的魔术方法（开辟空间）
触发时机：在实例化时触发
在对象实例化时，__new__方法先被调用，然后是__init__方法

__call__:对象调用方法（想不想将对象当成函数使用）
触发时机：将对象当成函数使用的时候会默认调用此函数中的内容

__del__:（没有指针引用的时候会调用，99%都不需要重写）
    1.对象赋值
      p = Person()
      p1 = p
      说明：p和p1共同指向同一个地址
    2. 删除地址的引用
      del p1   删除p1对地址的引用
    3.查看对地址的引用次数：
      import sys
      sys.getrefcount(p)
    4.当一块空间没有了任何引用，默认执行__del__
      ref = 0
      
__str__:
触发时机：打印对象名，自动触发去调用__str__里面的内容
注意：一定要在__str__方法中添加return，return后面就是打印对象看到的内容
"""


class Person:
    def __init__(self):
        print("-------> init")
        # self.name = name

    # __new__方法用于创建对象实例,向内存申请空间，产生地址
    def __new__(cls, *args, **kwargs):
        print("-----> new")
        # object.__new__()是一个静态方法，用于创建对象实例，只接受一个参数，即类本身(cls)
        return object.__new__(cls)  # *args, **kwargs在这里可以不用传入（python3.6的改进）

    # 将对象当做函数来使用需要写入__init__()方法
    def __call__(self, *args, **kwargs):  # 可以在里面传入多个参数
        print("-------> call")


p = Person()
p()  # 将实例当函数调用

"""
方法之间的调用：
class A：
    def a(self):
        pass
    def b(self):
        # 调用a方法
        self.a()
"""

import sys


class Person:
    def __init__(self, name):
        self.name = name

    def __del__(self):
        print("-------del-------")


p = Person("jack")
p1 = p  # 对象赋值
p2 = p
print(p1.name)
print(p2.name)

p1.name = "white"  # p1改变了内存中的数据，p和p2调用时都会发生变化
print(p2.name)
print(p.name)

del p2
print(p.name)
del p1
print(p.name)  # 断掉了p1和p2的连接，p的内存地址依然存在
# del p
# print(p.name)

print(sys.getrefcount(p))  # 调用sys库中的函数来统计对象p的调用次数（检查时还默认调了一次，减一即可）

# 当p的内存空间被清完后，__del__会运行完，再打印n值;没清完则先打印n值，最后调用__del__
n = 5
print(n, "\n")


# 单纯打印对象名称，出来的是一个地址，地址对于开发者来说没有太大意义
# 方法__str__可以在打印对象名时给开发者更多的一些信息量
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return "姓名是：" + self.name + ",年龄：" + str(self.age)


p5 = Person("tom", 18)
print(p5, "\n")